#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <termios.h>


#define STDIN_FD	0
#define STDOUT_FD	1
#define BUFFER_SIZE	8096u
#define EXIT_STR	"exit"


static int dev_fd = -1;
static pthread_t dev_listener_ptid;
static pthread_t stdin_listener_ptid;
static pthread_cond_t end_cond;
static pthread_mutex_t end_cond_mtx;


void* dev_listener(void *arg)
{
	int rd, wr, pos;
	char buffer[BUFFER_SIZE];

	for ( ;; )
	{
		rd = read(dev_fd, buffer, BUFFER_SIZE);
		if (rd < 0)
		{
			perror("## Erreur : read() on device - ");
			break;
		}

		pos = 0;
		while (rd > 0)
		{
			wr = write(STDOUT_FD, &buffer[pos], rd);
			if (wr < 0)
			{
				perror("## Erreur : write() on STDOUT - ");
				break;
			}
			pos += wr;
			rd -= wr;
		}
	}

	pthread_cond_signal(&end_cond);
	pthread_exit(NULL);
}

void* stdin_listener(void *arg)
{
	int rd, wr, pos;
	char buffer[BUFFER_SIZE];

	for ( ;; )
	{
		rd = read(STDIN_FD, buffer, BUFFER_SIZE);
		if (rd < 0)
		{
			perror("## Erreur : read() on STDIN - ");
			break;
		}

		buffer[rd-1] = 0;	// On remplace le retour à la ligne par 0

		if (strncmp(buffer, "exit", rd) == 0)
		{
			break;
		}

		pos = 0;
		while (rd > 0)
		{
			wr = write(dev_fd, &buffer[pos], rd);
			if (wr < 0)
			{
				perror("## Erreur : write() on device - ");
				break;
			}
			pos += wr;
			rd -= wr;
		}
	}

	pthread_cond_signal(&end_cond);
	pthread_exit(NULL);
}

void do_exit(void)
{
	pthread_cancel(dev_listener_ptid);
	if (pthread_join(dev_listener_ptid, NULL) != 0)
	{
		perror("## Erreur : pthread_join() : device listener - ");
	}
	
	pthread_cancel(stdin_listener_ptid);
	if (pthread_join(stdin_listener_ptid, NULL) != 0)
	{
		perror("## Erreur : pthread_join() : device listener - ");
	}

	if (dev_fd != -1)
	{	
		if (close(dev_fd) != 0)
		{
			perror("## Erreur : close() - ");
		}
	}

	pthread_exit(NULL);
}

int main(int argc, char **argv)
{
	struct termios options;

	if (argc != 2)
	{
		printf("## Erreur : nombre d'arguments incorrecte.\n");
		printf("## Utilisation ListenRaspB <dev>\n");
		printf("## Exemple : LisenRaspB /dev/tty.usb0\n");
		do_exit();
	}

	dev_fd = open(argv[1], O_RDWR | O_NONBLOCK | O_NOCTTY);
	if (dev_fd < 0)
	{
		perror("## Erreur : open() - ");
		do_exit();
	}
	
	if (fcntl(dev_fd, F_SETFL, 0) < 0)
	{
		perror("## Erreur : fcntl() 1 - ");
		do_exit();
	}

	if (tcgetattr(dev_fd, &options) < 0)
	{
		perror("## Erreur : tcgetattr() - ");
		do_exit();
	}

	if ( (cfsetispeed(&options, B115200) < 0)
		|| (cfsetospeed(&options, B115200) < 0) )
	{
		perror("## Erreur : cfsetospeed() ou cfsetispeed - ");
		do_exit();
	}

	options.c_cflag = ( CS8 | CREAD  | CLOCAL );
	options.c_oflag = 0;
	options.c_iflag = 0;
	options.c_lflag = 0;

	options.c_cc[VTIME] = 0;
    options.c_cc[VMIN] = 1;

	if (tcflush(dev_fd, TCIOFLUSH) < 0)
	{
		perror("## Erreur : tcflush() - ");
		do_exit();
	}

	if (tcsetattr(dev_fd, TCSANOW, &options) < 0)
	{
		perror("## Erreur : tcsetattr() - ");
		do_exit();
	}

	if (pthread_mutex_init(&end_cond_mtx, NULL) < 0)
	{
		perror("## Erreur : pthread_create() stdin listener - ");
		do_exit();
	}

	if (pthread_cond_init(&end_cond, NULL) < 0)
	{
		perror("pthread_cond_init() - ");
		do_exit();
	}

	if (pthread_create(&dev_listener_ptid, NULL, dev_listener, NULL) < 0)
	{
		perror("## Erreur : pthread_create() device listener - ");
		do_exit();
	}

	if (pthread_create(&stdin_listener_ptid, NULL, stdin_listener, NULL) < 0)
	{
		perror("## Erreur : pthread_create() stdin listener - ");
		do_exit();
	}

	pthread_mutex_lock(&end_cond_mtx);
	pthread_cond_wait(&end_cond, &end_cond_mtx);
	pthread_mutex_unlock(&end_cond_mtx);

	do_exit();
}
